package com.cn.tous.auth.config;

import com.cn.tous.auth.config.processor.Oauth2Processor;
import com.cn.tous.auth.event.NullEventPublisher;
import com.cn.tous.auth.oauth2.token.JwtTokenJwtCustomizer;
import com.cn.tous.auth.security.PasswordAuthenticationConverter;
import com.cn.tous.auth.security.PasswordAuthorizationServerContext;
import com.cn.tous.auth.security.PasswordOAuth2AuthorizationGrantAuthenticationProvider;
import com.cn.tous.auth.security.exception.CustomAuthenticationExceptionHandler;
import com.cn.tous.auth.security.exception.CustomAuthorizationExceptionHandler;
import com.cn.tous.auth.security.exception.CustomSecurityExceptionHandler;
import com.cn.tous.auth.service.RedisOAuth2AuthorizationService;
import com.cn.tous.auth.service.UserDetailsServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.Filter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.savedrequest.NullRequestCache;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * @author mengwei
 * @description AuthorizationServerConfig
 * @createDate 2025/7/29 10:41
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class AuthorizationServerConfig {

    private final AuthenticationEntryPoint authenticationExceptionHandler = new CustomAuthenticationExceptionHandler();
    private final AccessDeniedHandler authorizationExceptionHandler = new CustomAuthorizationExceptionHandler();
    private final Filter globalSpringSecurityExceptionHandler = new CustomSecurityExceptionHandler();

    private final Oauth2Processor oauth2Processor;

    private final UserDetailsServiceImpl userDetailsService;

    private final PasswordAuthorizationServerContext context;

    private final ApplicationContext applicationContext;



//    private final OAuth2AuthorizationService authorizationService;
    // 1. 配置授权服务器的主安全过滤器链
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, OAuth2AuthorizationService authorizationService) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();
        OAuth2TokenGenerator<?> oAuth2TokenGenerator = tokenGenerator(jwkSource());


        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer, (authorizationServer) ->
                        authorizationServer
                                // Enable OpenID Connect 1.0
                                .oidc(Customizer.withDefaults())
                                .tokenGenerator(oAuth2TokenGenerator)
                                .authorizationService(authorizationService)

                                .authorizationEndpoint(
                                        // 配置授权同意页面 (可选，用于授权码模式)
                                        authorizationEndpoint ->
                                        {
                                            try {
                                                authorizationEndpoint.consentPage("/oauth2/consent")
                                                        .authenticationProvider(new JwtAuthenticationProvider(jwtDecoder(jwkSource())))
                                                        ;

                                            } catch (Exception e) {
                                                throw new RuntimeException(e);
                                            }
                                        }
                                )
                );
        http.oauth2ResourceServer(rs -> rs
                        .jwt(withDefaults())

                );
        http.authorizeHttpRequests((authorize) ->
                authorize
                        .anyRequest().authenticated()
        );
        OAuth2TokenEndpointFilter passwordTokenEndpointFilter = passwordTokenEndpointFilter(oAuth2TokenGenerator);
        http.addFilterBefore(passwordTokenEndpointFilter, UsernamePasswordAuthenticationFilter.class);
        // 处理未认证请求

        // 处理 SpringSecurity 异常响应结果。响应数据的结构，改成业务统一的JSON结构。不要框架默认的响应结构
        http.exceptionHandling(exceptionHandling ->
                exceptionHandling
                        // 认证失败异常
                        .authenticationEntryPoint(authenticationExceptionHandler)
                        // 鉴权失败异常
                        .accessDeniedHandler(authorizationExceptionHandler)
        );
        commonHttpSetting(http);

        return http.build();
    }


    // 2. 配置普通应用的安全过滤器链 (例如 /login 页面)
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        commonHttpSetting(http);
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login", "/register").permitAll()
                        .anyRequest().authenticated()
                )
                .httpBasic(AbstractHttpConfigurer::disable) // 禁用 HTTP Basic
                .oauth2ResourceServer(rs -> rs
                        .jwt(withDefaults()) // 使用默认 JWT 解码器
                )

                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        ;

        return http.build();
    }


    private void commonHttpSetting(HttpSecurity http) throws Exception {
        // 禁用SpringSecurity默认filter。这些filter都是非前后端分离项目的产物，用不上.
        // 表单登录/登出、session管理、csrf防护等默认配置，如果不disable。会默认创建默认filter
        http.formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                // requestCache用于重定向，前后端分析项目无需重定向，requestCache也用不上
                .requestCache(cache -> cache
                        .requestCache(new NullRequestCache())
                )
                // 无需给用户一个匿名身份
                .anonymous(AbstractHttpConfigurer::disable);

        // 处理 SpringSecurity 异常响应结果。响应数据的结构，改成业务统一的JSON结构。不要框架默认的响应结构
        http.exceptionHandling(exceptionHandling ->
                exceptionHandling
                        // 认证失败异常
                        .authenticationEntryPoint(authenticationExceptionHandler)
                        // 鉴权失败异常
                        .accessDeniedHandler(authorizationExceptionHandler)
        );
        // 其他未知异常. 尽量提前加载。
        http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);
    }




    // 3. 注册客户端信息 (使用 JdbcRegisteredClientRepository 持久化)
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        // 1. 创建 RegisteredClient
        RegisteredClient.Builder clientBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId(oauth2Processor.getClientId())
                .clientSecret(passwordEncoder.encode(oauth2Processor.getClientSecret())) // 加密存储
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                // 授权类型
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
        // 动态添加密码模式 (如果配置中包含)
        if (oauth2Processor.getGrantTypes().contains("password")) {
            // 注意：Spring Authorization Server 1.1+ 不再内置支持密码模式。
            // 我们需要手动扩展，见下方 `passwordAuthenticationProvider` 配置。
            // 这里先注册类型，但实际处理需要自定义。
            clientBuilder.authorizationGrantType(new AuthorizationGrantType("password"));
        }

        // 重定向 URI
        for (String redirectUri : oauth2Processor.getRedirectUris()) {
            clientBuilder.redirectUri(redirectUri.trim());
        }

        // 授权范围
        for (String scope : oauth2Processor.getScopes()) {
            clientBuilder.scope(scope.trim());
        }

        // 客户端设置
        ClientSettings clientSettings = ClientSettings.builder()
                .requireAuthorizationConsent(true) // 需要用户授权同意 (授权码模式)
                .build();

        // 令牌设置
        TokenSettings tokenSettings = TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofSeconds(oauth2Processor.getAccessTokenTtl()))
                .refreshTokenTimeToLive(Duration.ofSeconds(oauth2Processor.getRefreshTokenTtl()))
                .build();

        RegisteredClient registeredClient = clientBuilder
                .clientSettings(clientSettings)
                .tokenSettings(tokenSettings)
                .build();

        // 2. 使用 JDBC 存储客户端
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        // 检查客户端是否存在，不存在则创建
        if (registeredClientRepository.findByClientId(oauth2Processor.getClientId()) == null) {
            registeredClientRepository.save(registeredClient);
        }

        return registeredClientRepository;
    }

    // 4. 配置 JWK Source (用于 JWT 签名)
    @Bean
    public JWKSource<SecurityContext> jwkSource() throws Exception {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    private static KeyPair generateRsaKey() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.generateKeyPair();
    }

    // 5. 配置 JWT 解码器 (用于资源服务器验证)
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    // 6. 配置 AuthorizationServerSettings
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer(oauth2Processor.getIssuer())
                .build();
    }

    // 7. 配置 OAuth2AuthorizationService (持久化授权信息)
    @Bean
    public OAuth2AuthorizationService redisOAuth2AuthorizationService(RedisTemplate<String, Object> redisTemplate, ObjectMapper objectMapper, JdbcTemplate jdbcTemplate) {
        RegisteredClientRepository registeredClientRepository = registeredClientRepository(jdbcTemplate, passwordEncoder());
        return new RedisOAuth2AuthorizationService(redisTemplate, objectMapper ,registeredClientRepository, oauth2Processor.getIssuer());
    }

    // 8. 配置 OAuth2AuthorizationConsentService (持久化授权同意)
//    @Bean
//    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
//        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
//    }

    // 9. 配置用户详情服务 (UserDetailsService) - 使用数据库


//    @Bean
//    public UserDetailsService userDetailsService(DataSource dataSource, PasswordEncoder passwordEncoder) {
//        // 创建用户表和插入默认用户 (可选，也可以用 JdbcUserDetailsManager)
//        JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(dataSource);
//        userDetailsManager.setCreateUserSql(JdbcDaoImpl.CREATE_USER_SQL);
//        userDetailsManager.setDeleteUserSql(JdbcDaoImpl.DELETE_USER_SQL);
//        userDetailsManager.setUpdateUserSql(JdbcDaoImpl.UPDATE_USER_SQL);
//        userDetailsManager.setChangePasswordSql(JdbcDaoImpl.CHANGE_PASSWORD_SQL);
//        userDetailsManager.setCreateAuthoritySql(JdbcDaoImpl.CREATE_AUTHORITY_SQL);
//        userDetailsManager.setDeleteUserAuthoritiesSql(JdbcDaoImpl.DELETE_USER_AUTHORITIES_SQL);
//        userDetailsManager.setAddUserAuthoritySql(JdbcDaoImpl.ADD_USER_TO_AUTHORITY_SQL);
//        userDetailsManager.setFindUserAuthoritiesSql(JdbcDaoImpl.FIND_AUTHORITY_SQL);
//
//        // 创建用户 (如果不存在)
//        if (!userDetailsManager.userExists("user")) {
//            UserDetails user = User.builder()
//                    .username("user")
//                    .password(passwordEncoder.encode("password")) // 密码: password
//                    .authorities("ROLE_USER", "SCOPE_read", "SCOPE_write")
//                    .build();
//            userDetailsManager.createUser(user);
//        }
//        if (!userDetailsManager.userExists("admin")) {
//            UserDetails admin = User.builder()
//                    .username("admin")
//                    .password(passwordEncoder.encode("adminpass"))
//                    .authorities("ROLE_ADMIN", "ROLE_USER", "SCOPE_read", "SCOPE_write", "SCOPE_admin")
//                    .build();
//            userDetailsManager.createUser(admin);
//        }
//        return userDetailsManager;
//    }

    // 10. 配置密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance(); // 仅用于演示！生产环境用 BCryptPasswordEncoder
    }





    // 11. 注册自定义的密码模式认证提供者
    @Bean
    public ProviderManager passwordAuthenticationProviderManager(
            OAuth2TokenGenerator<?> tokenGenerator, UserDetailsServiceImpl userDetailsServiceImpl
            , AuthorizationServerContext context) {
        OAuth2AuthorizationService authorService = applicationContext.getBean(OAuth2AuthorizationService.class);
        // 为 Provider 注入必要的依赖
        PasswordOAuth2AuthorizationGrantAuthenticationProvider provider =
                new PasswordOAuth2AuthorizationGrantAuthenticationProvider(tokenGenerator, userDetailsServiceImpl
                        , passwordEncoder(), context, authorService);

        ProviderManager providerManager = new ProviderManager(provider);
        providerManager.setAuthenticationEventPublisher(new NullEventPublisher());
        return providerManager;
    }

    // 13. 配置 OAuth2TokenEndpointFilter 以支持 password grant
    @Bean
    public OAuth2TokenEndpointFilter passwordTokenEndpointFilter(
            OAuth2TokenGenerator<?> tokenGenerator
            ) {

        // 创建支持 password grant 的 AuthenticationConverter
        PasswordAuthenticationConverter passwordConverter =
                new PasswordAuthenticationConverter();

        // 创建 ProviderManager (包含我们的自定义 Provider)
        ProviderManager providerManager = passwordAuthenticationProviderManager(tokenGenerator, userDetailsService, context);

        // 创建 OAuth2TokenEndpointFilter
        OAuth2TokenEndpointFilter filter = new OAuth2TokenEndpointFilter(providerManager );
        // 设置处理 password grant 的 ProviderManager
        filter.setAuthenticationConverter(passwordConverter);


        return filter;
    }


    // 15. 配置 TokenGenerator (包含 JWT Encoder)
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator( JWKSource<SecurityContext> jwkSource) {
        JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
//        OAuth2PasswordTokenGenerator passwordTokenGenerator = new OAuth2PasswordTokenGenerator();
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        // 自定义 AccessToken 生成器可以添加更多 Claim
        jwtGenerator.setJwtCustomizer(new JwtTokenJwtCustomizer());
        return new DelegatingOAuth2TokenGenerator(
//                accessTokenGenerator,
                refreshTokenGenerator,
                jwtGenerator
        );
    }

}
